package shyre.powercraft.viewer;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.*;
import org.bukkit.entity.Player;
import org.bukkit.block.*;
import org.bukkit.Chunk;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.event.Event.Priority;
import org.bukkit.event.Event;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.PluginManager;
import java.net.Socket;

/**
* Viewer plugin for Bukkit
*
* @author Shyre
*/
public class Viewer extends JavaPlugin {
	
    private final VRPlayerListener playerListener = new VRPlayerListener(this);
    private final VRBlockListener blockListener = new VRBlockListener(this);
    
    private Socket client;
    private OutputStream oStream;
	private BufferedOutputStream bOStream;
	
	private static int readIO_counter;
	private static int getData_counter;
	private static int update_counter;
	private Thread thread_readInput;
	private Thread thread_getData;
	private Thread thread_update;
	
    public static final int SERVER_PORT = 8000;
    public static final int GET_CHUNK_LIMIT = 1;
    public static final int NB_BLOCKS_IN_CHUNK = 28575;
    public static final int MAX_X = 15;
    public static final int MAX_Y = 127;
    public static final int MAX_Z = 15;
    
    class Block_t {
    	public Block_t(int x, int y, int z, int id) {
    		this.x = x;
    		this.y = y;
    		this.z = z;
    		this.id = id;
    	}
    	public int x, y, z;
    	public int id;
    }
    class Chunk_t {
    	public Chunk_t(int x, int z) {
    		this.x = x;
    		this.z = z;
    	}
    	public int x, z;
    	public List<Block_t> blocks;
    }
    class Chunk_xz {
    	Chunk_xz(int x, int z) {
    		this.x = x;
    		this.z = z;
    	}
    	public int x, z;
    }
    
    private List<Chunk_xz> data;
    private List<Chunk_t> newChunks;
    private List<Chunk_t> maj;
    
    public class ReadInput extends Thread {
    	private byte[] b = new byte[256];
    	
        public void run() {
        	readIO_counter++;
        	int bitsRecus = 0;
        	try {
        		InputStream iStream = client.getInputStream();
        		BufferedInputStream bIStream = new BufferedInputStream(iStream);
        		while(client != null && (bitsRecus = bIStream.read(b)) >= 0)
    			{
    				System.out.println("On a recu : " + bitsRecus + " bits");
    				System.out.println("Recu : " + new String(b, 0, bitsRecus));
    			}
        		client.close();
        		client = null;
        		bIStream.close();
        		System.out.println("Disconnected from server");
            } catch (IOException e) {
            	client = null;
            }
        	readIO_counter--;
        }
    }
    public class GetData extends Thread {
    	private Block b;
    	public GetData(Object arg) {
    		b = (Block)arg;
    	}
    	public void run() {
    		getData_counter++;
    		World world = b.getWorld();
        	Chunk chunk = b.getChunk();
        	
        	int cx = chunk.getX(), cz = chunk.getZ();

        	for(int i=cx-GET_CHUNK_LIMIT; i<cx+GET_CHUNK_LIMIT; i++)
        	{
        		for(int j=cz-GET_CHUNK_LIMIT; j<cz+GET_CHUNK_LIMIT; j++)
        		{
        			if(world.isChunkLoaded(i, j))
        			{
        				if(!isChunkNew(i, j))
        				{
    		    			chunk = world.getChunkAt(i, j);
    		    			addNewChunk(chunk);
        				}
        				else if(cx == i && cz == j)
        					addBlockToMaj(b, cx, cz);
        			}
        		}
        	}
        	getData_counter--;
    	}
    }
    public class Update extends Thread {
    	public void run() {
    		update_counter++;
    		if(client == null)
        	{
        		try {
            		client = new Socket("localhost", SERVER_PORT);
            		oStream = client.getOutputStream();
        			bOStream = new BufferedOutputStream(oStream);
        			if(readIO_counter == 0)
        			{
        				thread_readInput = new ReadInput();
        				thread_readInput.start();
        			}
            		System.out.println("[Viewer] connected to localhost on port 8000");
            	}
            	catch(IOException e) {
            		client = null;
            	}
        	}
        	if(client != null)
        	{
        		int dataSize = data.size();
        		int newChunksSize = newChunks.size();
        		int majSize = maj.size();
        		System.out.println("data = " + dataSize);
            	System.out.println("newChunks = " + newChunksSize);
            	System.out.println("maj = " + majSize);
            	try {
                	char c = 10;
        			bOStream.write(c);
        			bOStream.write(intToByteArray(newChunksSize));
        			Iterator<Chunk_t> it = newChunks.iterator();
        	    	while(it.hasNext())
        	    	{
        	    		Chunk_t ch = (Chunk_t)it.next();
        	    		int nbBlocks = ch.blocks.size();
        	    		bOStream.write(intToByteArray(nbBlocks));
        	    		bOStream.write(intToByteArray(ch.x));
    	    			bOStream.write(intToByteArray(ch.z));
        	    		Iterator<Block_t> _it = ch.blocks.iterator();
        	    		while(_it.hasNext())
        	    		{
        	    			Block_t b = (Block_t)_it.next();
        	    			bOStream.write(intToByteArray(b.x));
        	    			bOStream.write(intToByteArray(b.y));
        	    			bOStream.write(intToByteArray(b.z));
        	    			bOStream.write(intToByteArray(b.id));
        	    		}
        	    	}
        	    	bOStream.flush();
        	    	newChunks.clear();
            	}
            	catch(IOException e) {
            		client = null;
            	}
            	try {
            		char c = 20;
        			bOStream.write(c);
        			bOStream.write(intToByteArray(majSize));
        			Iterator<Chunk_t> it = maj.iterator();
        	    	while(it.hasNext())
        	    	{
        	    		Chunk_t ch = (Chunk_t)it.next();
        	    		int nbBlocks = ch.blocks.size();
        	    		bOStream.write(intToByteArray(nbBlocks));
        	    		bOStream.write(intToByteArray(ch.x));
    	    			bOStream.write(intToByteArray(ch.z));
        	    		Iterator<Block_t> _it = ch.blocks.iterator();
        	    		while(_it.hasNext())
        	    		{
        	    			Block_t b = (Block_t)_it.next();
        	    			bOStream.write(intToByteArray(b.x));
        	    			bOStream.write(intToByteArray(b.y));
        	    			bOStream.write(intToByteArray(b.z));
        	    			bOStream.write(intToByteArray(b.id));
        	    		}
        	    	}
        	    	bOStream.flush();
        	    	maj.clear();
            	}
            	catch(IOException e) {
            		client = null;
            	}
        	}
    		update_counter--;
    	}
    }
    
    public static final byte[] intToByteArray(int value) {
        return new byte[] {
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)(value)
                };
    }

    public void onEnable() {
    	maj = new ArrayList<Chunk_t>();
    	newChunks = new ArrayList<Chunk_t>();
    	data = new ArrayList<Chunk_xz>();
    	
    	readIO_counter = 0;
    	getData_counter = 0;
    	update_counter = 0;
    	
    	if(update_counter == 0)
    	{
    		thread_update = new Update();
    		thread_update.start();
    	}
    	
        PluginManager pm = getServer().getPluginManager();
        pm.registerEvent(Event.Type.BLOCK_BREAK, blockListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.BLOCK_PLACED, blockListener, Priority.Normal, this);
        PluginDescriptionFile pdfFile = this.getDescription();
        System.out.println( "["+pdfFile.getName()+"]" + " version " + pdfFile.getVersion() + " is enabled" );
    }
    
    public void onDisable() {
    	try {
    		if(thread_getData != null)
    			thread_getData.join();
    		if(thread_update != null)
    			thread_update.join();
    		if(thread_readInput != null)
    		{
    			if(client != null)
    				client.close();
    	    	thread_readInput.join();
    		}
    	}
    	catch(InterruptedException e) {
    	}
    	catch(IOException e) {
    		client = null;
    	}
    	PluginDescriptionFile pdfFile = this.getDescription();
        System.out.println( "["+pdfFile.getName()+"]" + " version " + pdfFile.getVersion() + " is disabled" );
    }
    
    public boolean isChunkNew(int x, int z)
    {
    	boolean exist = false;
    	for (Chunk_xz c : data) {
    		 if(c.x == x && c.z == z)
    			 exist = true;
    	}
    	return exist;
    }
    
    public void addNewChunk(Chunk c)
    {
    	Chunk_t chunk = new Chunk_t(c.getX(), c.getZ());
    	chunk.blocks = new ArrayList<Block_t>(NB_BLOCKS_IN_CHUNK);
    	Block block = null;
    	for(int x=0; x<MAX_X; x++)
    	{
    		for(int y=0; y<MAX_Y; y++)
    		{
    			for(int z=0; z<MAX_Z; z++)
    			{
    				block = c.getBlock(x, y, z);
    				Block_t newb = new Block_t(block.getX(), block.getY(), block.getZ(), block.getTypeId());
    				synchronized(chunk.blocks) {
    					chunk.blocks.add(newb);
    				}
    			}
    		}
    	}
    	synchronized(newChunks) {
    		newChunks.add(chunk);
    	}
    	synchronized(data) {
    		data.add(new Chunk_xz(c.getX(), c.getZ()));
    	}
    }
    
    public void addBlockToMaj(Block b, int x, int z)
    {
    	boolean exist = false;
    	Iterator<Chunk_t> it = maj.iterator();
    	while(it.hasNext() && !exist)
    	{
    		Chunk_t c = (Chunk_t)it.next();
    		 if(c.x == x && c.z == z)
    		 {
    			 c.blocks.add(new Block_t(b.getX(), b.getY(), b.getZ(), b.getTypeId()));
    			 exist = true;
    		 }
    	}
    	if(!exist)
    	{
    		Chunk_t c = new Chunk_t(x, z);
    		c.blocks = new ArrayList<Block_t>();
    		synchronized(c.blocks) {
    			c.blocks.add(new Block_t(b.getX(), b.getY(), b.getZ(), b.getTypeId()));
    		}
    		synchronized(maj) {
    			maj.add(c);
    		}
    	}
    }
    
    public void getData(Block b)
    {
    	if(getData_counter == 0)
    	{
    		thread_getData = new GetData(b);
    		thread_getData.start();
    	}
    	if(update_counter == 0)
    	{
    		thread_update = new Update();
    		thread_update.start();
    	}
    	
    }
}